﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Deployment.Application;

using UlsStudio.AppCore;
using UlsStudio.Engine.SqlParsers;
using UlsStudio.Engine.Events;
using UlsStudio.Engine;

namespace UlsStudio.DesktopApp
{
    /// <summary>
    /// The main application form.
    /// </summary>
    public partial class MainForm : Form
    {
        // Plumbing
        #region Constructor & Privates

        DateTime lastDisplayUpdate = DateTime.MinValue;
        private string ENTRY_COUNT_LABEL_VALUE = string.Empty;

        private readonly ApplicationEngine _appEngine = null;

        /// <summary>
        /// Form constructor
        /// </summary>
        public MainForm()
        {
            InitializeComponent();
            this._appEngine = new ApplicationEngine();
            this.SearchSettings = new GridSearchSettings();
            this.UserQueryBeingAppliedSoDontAskAgain = false;       // Set true in 'MainForm_NewFilterBeingSet' if query is already dirty

            // Bind events
            this.AppEngine.GridHelper.NewRecordSelected += GridHelper_NewRecordSelected;
            this.AppEngine.GridHelper.WantedLogEntryLoaded += GridHelper_WantedLogEntryLoaded;
            this.AppEngine.FilterValidated += AppEngine_FilterValidated;
            this.AppEngine.NewStatistics += AppEngine_NewStatistics;
            this.NewFilterBeingSet += MainForm_NewFilterBeingSet;

            this.lstLogs.DragEnter += Form_DragEnter;
            this.lstLogs.DragDrop += Form_DragDrop;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Engine of the app
        /// </summary>
        public ApplicationEngine AppEngine
        {
            get { return _appEngine; }
        }

        /// <summary>
        /// X of Y entries loaded - for use while loads are in progress only
        /// </summary>
        public string LastRecordLoadedStats { get; set; }

        /// <summary>
        /// Used to determine if to make refresh button enbaled/disabled
        /// </summary>
        public bool RefreshStatsPressed { get; set; }

        /// <summary>
        /// This query and grid will have an ID that needs to be selected (used to determine if ID will also be in results-set)
        /// </summary>
        public bool WaitForLastSelectedEntry { get; set; }

        /// <summary>
        /// Whether we've loaded for the 1st time yet
        /// </summary>
        public bool AppLoaded { get; set; }

        /// <summary>
        /// Last record count from stats
        /// </summary>
        public int LastUnfilteredRecordCount { get; set; }

        /// <summary>
        /// When the 'cancelled' event is caught next, close app?
        /// </summary>
        public bool CloseAppOnCancelled { get; set; }


        /// <summary>
        /// Debug
        /// </summary>
        public bool EnableGridConsoleOut { get; set; }

        /// <summary>
        /// The plain-text version of last known good query
        /// </summary>
        public string LastGoodQueryText { get; set; }

        /// <summary>
        /// Query in textbox has been applied (not dirty), or it has (is dirty)
        /// </summary>
        public bool QueryIsDirty { get; set; }

        public GridSearchSettings SearchSettings { get; set; }

        #endregion

        // Events
        #region Form Event Handlers

        #region Main Business Events
        
        void AppEngine_NewStatistics(object sender, UlsStudio.AppCore.Events.StatisticsEventArgs e)
        {
            // Do stuff on main thread, assuming this isn't an app shutdown
            if (this.IsDisposed) { return; }

            // Update GUI on mainthread
            if (InvokeRequired)
            {
                try
                {
                    Invoke(new Action<object, UlsStudio.AppCore.Events.StatisticsEventArgs>(AppEngine_NewStatistics), sender, e);
                }
                catch (ObjectDisposedException) { } // Ignore
                return;
            };

            

            // Check we're not closing since the thread came back
            if (this.IsDisposed) return;

            // Process stats
            ProcessNewStats(e.Statistics);
        }

        void AppEngine_FilterValidated(object sender, UlsStudio.AppCore.Events.QueryValidationResultEventArgs e)
        {
            // Do stuff on main thread, assuming this isn't an app shutdown
            if (this.IsDisposed) { return; }

            // Update GUI on mainthread
            if (InvokeRequired)
            {
                try
                {
                    Invoke(new Action<object, UlsStudio.AppCore.Events.QueryValidationResultEventArgs>(AppEngine_FilterValidated), sender, e);
                }
                catch (ObjectDisposedException) { } // Ignore

                return;
            };

            // Check we're not closing since the thread came back
            if (this.IsDisposed) return;

            FilterValidationFinished(e);
        }

        /// <summary>
        /// Last selected record has been loaded
        /// </summary>
        void GridHelper_NewRecordSelected(AbstractUlsLogEntry loadedItem)
        {
            // Update GUI on mainthread
            if (InvokeRequired)
            {
                try
                {
                    Invoke(new Action<AbstractUlsLogEntry>(GridHelper_NewRecordSelected), loadedItem);
                }
                catch (ObjectDisposedException)
                {
                    // Ignore
                }

                return;
            };

            if (loadedItem != null && loadedItem is DatabaseLoadedUlsLogEntry)
            {
                DatabaseLoadedUlsLogEntry dbItem = (DatabaseLoadedUlsLogEntry)loadedItem;
                txtMessage.Text = dbItem.Message;

                // Remember ID
                if (!WaitForLastSelectedEntry)
                {
                    this.AppEngine.GridHelper.LastSelectedID = dbItem.DatabaseID;
                    Logging.Writeline("MainForm.GridHelper_NewRecordSelected - Remembering selected ID {0} for query change.", this.AppEngine.GridHelper.LastSelectedID);
                }
            }
            else
            {
                Logging.Writeline("MainForm.GridHelper_NewRecordSelected - Null/invalid item returned.");
            }
        }

        /// <summary>
        /// App engine reports we have a new status.
        /// </summary>
        void loader_HaveNewLoadStatus(object sender, EventArgs e)
        {
            // There's data...go get it for this load operation
            ScanSettings settingsInUse = null;
            if (this.AppEngine.LoadMode == LoadMode.FileSet)
            {
                settingsInUse = this.AppEngine.FileSetLoadSettings;
            }
            else
            {
                settingsInUse = this.AppEngine.RealTimeSettings;
            }
            AbstractLoadingStatus status = this.AppEngine.UlsData.GetLoadStatus(settingsInUse);

            // Process new loading status
            NewLoadingStatus(status);
        }

        /// <summary>
        /// Load was cancelled
        /// </summary>
        void UlsLoader_LoadCancelled(object sender, EventArgs e)
        {
            // Do stuff on main thread, assuming this isn't an app shutdown
            if (this.IsDisposed)
            {
                return;
            }

            // Update GUI on mainthread
            if (InvokeRequired)
            {
                Invoke(new Action<object, EventArgs>(UlsLoader_LoadCancelled), sender, e);
                return;
            };

            // Set GUI
            SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting));
            BeginUpdateRecordStats(true, ApplicationState.Waiting);

            // Is this cancel because of a app-shutdown? If so, close
            if (this.CloseAppOnCancelled)
            {
                this.Close();
            }

        }

        #endregion

        #region Form

        private void MainForm_Load(object sender, EventArgs e)
        {
            // Set GUI
            pnlBored.Bounds = lstLogs.Bounds;

            // Title
#if (DEBUG)
            this.Text += " [DEBUG]";
#endif

            // Load settings
            SimpleSqlSelectStatementInfo lastSavedQuery = new SimpleSqlSelectStatementInfo(Properties.Settings.Default.LastQuery);

            // Load last query
            if (this.AppEngine.SetDisplayFilter(lastSavedQuery))
            {
                // Nothing, query is good
            }
            else
            {
                // Set default query
                ResetViewQueryToDefault(false);
            }


            // Misc
            originalSearchText = txtQuickSearch.Text;
            this.NewFilterBeingSet += MainForm_NewFilterBeingSet;
            this.UserQueryIsDirty += MainForm_UserQueryIsDirty;

#if (!DEBUG)
            dEBUGToolStripMenuItem.Visible = false;
#endif

            // Set loaded
            this.AppLoaded = true;

            // Vars/labels
            if (string.IsNullOrEmpty(MENU_HIDE_ORIGINAL_TEXT))
            {
                MENU_HIDE_ORIGINAL_TEXT = hideWhenItemEqualsXToolStripMenuItem.Text;
            }
            if (string.IsNullOrEmpty(MENU_ONLYSHOW_ORIGINAL_TEXT))
            {
                MENU_ONLYSHOW_ORIGINAL_TEXT = onlyShowItemsWhen01ToolStripMenuItem.Text;
            }

            // 1st run - create new dataset & hook up events
            this.AppEngine.UlsData.HaveNewLoadStatus += loader_HaveNewLoadStatus;
            this.AppEngine.UlsData.LoadCancelled += UlsLoader_LoadCancelled;
            this.AppEngine.UlsData.WatcherStarted += UlsData_WatcherStarted;
            this.AppEngine.UlsData.WatcherStopped += UlsData_WatcherStopped;



            // Final UI 
            CheckNewQueryIsValid();

            // Set display mode as "waiting" (not monitoring)
            SetApplicationDisplay(new DisplayStateUpdateInfo(MonitoringMode.NotRunning, 0));

            // Ready
            Logging.Writeline("MainForm.MainForm_Load - App ready.");
        }


        void MainForm_UserQueryIsDirty(object sender, EventArgs e)
        {
            SetFilterTextBoxColour(FilterState.Dirty);

            Logging.Writeline("Mainform.MainForm_UserQueryIsDirty");
        }

        public bool UserQueryBeingAppliedSoDontAskAgain { get; set; }

        void MainForm_NewFilterBeingSet(object sender, MainForm.NewFilterBeingSetEventArgs e)
        {
            // Confirm with user if they were half-way through setting a new query themselves (this likely being an auto query change)
            if (!this.UserQueryBeingAppliedSoDontAskAgain && this.QueryIsDirty)
            {
                bool reloadQuery = MessageBox.Show("The query changes you've made manually need to be executed & therefore verified before auto-filters can be applied.\n\n" + 
                    "Would you like to do this now? You'll need to re-apply the filter-change you've just made...",
                    "Change Filter", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes;

                if (reloadQuery)
                {
                    this.UserQueryBeingAppliedSoDontAskAgain = true;        // Don't ask again while applying this new filter
                    BeginSetDisplayFilter(new SimpleSqlSelectStatementInfo(txtFilter.Text), false);
                }

                e.Cancel = true;

            }
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (this.AppEngine.LoadInProgress)
            {
                if (MessageBox.Show("Cancel load?", "Load in progress", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == System.Windows.Forms.DialogResult.Yes)
                {
                    ((FileSetJobLoadingStatus)this.AppEngine.UlsData.LoadStatuses[this.AppEngine.FileSetLoadSettings]).UserCancelledLoad = true;        // Trigger abort
                    this.CloseAppOnCancelled = true;       // When 'cancelled' is raised, form will close
                }
                else
                {
                    return;
                }

                // Update GUI
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Cancelling));
            }

            // Disable watcher
            if (this.AppEngine.UlsData != null)
            {
                this.AppEngine.UlsData.StopLogMonitoring();
            }

        }

        void GridHelper_WantedLogEntryLoaded(AbstractUlsLogEntry e)
        {
            if (e is DatabaseLoadedUlsLogEntry)
            {
                DatabaseLoadedUlsLogEntry dbE = (DatabaseLoadedUlsLogEntry)e;
                //this.SelectIdxWhenAvailabe = dbE.RowID - 1;     // DB id == ViewIndex + 1
                this.WaitForLastSelectedEntry = true;
                Logging.Writeline("MainForm.GridHelper_WantedLogEntryLoaded - Found last selected record; awaiting grid to find it (in lstLogs_CellValueNeeded)");
            }

        }

        #endregion

        #region Clicks

        #region Toolbar Buttons

        private void btnFindInGrid_Click(object sender, EventArgs e)
        {
            NewGridSearch();
        }

        private void btnBack_Click(object sender, EventArgs e)
        {
            SimpleSqlSelectStatementInfo olderQuery = this.AppEngine.GoBackInHistory(1);
            ProcessNewDisplayQuery();
        }

        private void btnForward_Click(object sender, EventArgs e)
        {
            SimpleSqlSelectStatementInfo olderQuery = this.AppEngine.GoForwardInHistory(1);
            ProcessNewDisplayQuery();
        }

        private void btnRefreshStats_Click(object sender, EventArgs e)
        {
            this.RefreshStatsPressed = true;         // Stop the button from re-enabling on DisplayUpdate

            if (this.AppEngine.LoadInProgress)
            {
                BeginUpdateRecordStats(true, ApplicationState.Loading);
            }
            else
            {
                BeginUpdateRecordStats(true, ApplicationState.Waiting);
            }

        }

        private void btnSelectLoadSource_Click(object sender, EventArgs e)
        {
            OpenLogsDialogueAndStartLoading();
        }

        private void btnMonitorFarm_Click(object sender, EventArgs e)
        {
            OpenMonitoringDialogue();
        }

        private void toggleGridConsoleWriteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.EnableGridConsoleOut = !this.EnableGridConsoleOut;
            MessageBox.Show("EnableGridConsoleOut=" + EnableGridConsoleOut, "Debug", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void monitorFarmToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenMonitoringDialogue();
        }

        private void btnQuickSearch_Click(object sender, EventArgs e)
        {
            NewQuickSearchWanted();
        }

        private void btnFilterToggle_Click(object sender, EventArgs e)
        {
            UpdateQueryWithLevelAutoFilters();
        }

        private void btnAutoScroll_Click(object sender, EventArgs e)
        {
            Properties.Settings.Default.AutoScroll = btnAutoScroll.Checked;
            Properties.Settings.Default.Save();

            AutoScrollIfNecesary();
        }

        #endregion

        #region Options Menu
        private void showOnlyErrorsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SimpleSqlSelectStatementInfo newQuery = new SimpleSqlSelectStatementInfo(this.AppEngine.CurrentQuery);
            newQuery.WhereClauses.Clear();
            newQuery.WhereClauses.Add(new WhereClause("level != 'Information'"), FollowingOperatorType.And);
            newQuery.WhereClauses.Add(new WhereClause("level != 'Medium'"), FollowingOperatorType.And);
            newQuery.WhereClauses.Add(new WhereClause("level != 'Verbose'"), FollowingOperatorType.And);
            newQuery.WhereClauses.Add(new WhereClause("level != 'High'"), FollowingOperatorType.And);
            newQuery.WhereClauses.Add(new WhereClause("level != 'Monitorable'"), FollowingOperatorType.And);
            this.AppEngine.SetDisplayFilter(newQuery);
        }

        private void newGridSearchToolStripMenuItem_Click(object sender, EventArgs e)
        {
            NewGridSearch();
        }
        private void searchInGridFindNextToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (this.SearchSettings.IsSearchDefined)
            {
                SearchInGrid(this.SearchSettings);
            }
            else
            {
                NewGridSearch();
            }
        }

        private void optionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SettingsForm f = new SettingsForm();
            if (f.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                // Reset grid
                ProcessNewDisplayQuery();
            }
        }

        private void clearAllResultsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("Clear down all results & data loaded so far?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                == System.Windows.Forms.DialogResult.Yes)
            {
                ClearDisplayCache(true);
                RebuildListColumns();
                txtMessage.Text = string.Empty;
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting));

                if (this.AppEngine.LoadInProgress)
                {
                    BeginUpdateRecordStats(true, ApplicationState.Loading);
                }
                else
                {
                    BeginUpdateRecordStats(true, ApplicationState.Waiting);
                }
            }
        }
        private void applyQueryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ApplyUserFilter();
        }

        private void startMonitoringDevBoxToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.AppEngine.RealTimeSettings.Directories = new List<System.IO.DirectoryInfo>() { new System.IO.DirectoryInfo(@"\\sfb-sp14dev\c$\program files\Common Files\Microsoft Shared\Web Server Extensions\14\LOGS") };
            StartMonitoring(this.AppEngine.RealTimeSettings.Directories.Count);
        }

        /// <summary>
        /// Huge method, don't expand
        /// </summary>
        private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            UpdateCheckInfo info = null;

            if (ApplicationDeployment.IsNetworkDeployed)
            {
                ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;

                this.Cursor = Cursors.WaitCursor;
                try
                {
                    info = ad.CheckForDetailedUpdate();

                }
                catch (DeploymentDownloadException dde)
                {
                    this.Cursor = Cursors.Default;
                    MessageBox.Show("The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " + dde.Message,
                        "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    return;
                }
                catch (InvalidDeploymentException ide)
                {
                    this.Cursor = Cursors.Default;
                    MessageBox.Show("Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " + ide.Message,
                        "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    return;
                }
                catch (InvalidOperationException ioe)
                {
                    this.Cursor = Cursors.Default;
                    MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message,
                        "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    return;
                }

                if (info.UpdateAvailable)
                {
                    Boolean doUpdate = true;
                    this.Cursor = Cursors.Default;
                    if (!info.IsUpdateRequired)
                    {
                        DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?",
                            "Update Available", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
                        if (!(DialogResult.OK == dr))
                        {
                            doUpdate = false;
                        }
                    }
                    else
                    {
                        // Display a message that the app MUST reboot. Display the minimum required version.
                        MessageBox.Show("This application has detected a mandatory update from your current " +
                            "version to version " + info.MinimumRequiredVersion.ToString() +
                            ". The application will now install the update and restart.",
                            "Update Available", MessageBoxButtons.OK,
                            MessageBoxIcon.Information);
                    }

                    if (doUpdate)
                    {
                        try
                        {
                            ad.Update();
                            //MessageBox.Show("The application has been upgraded, and will now restart.", "Done", MessageBoxButtons.OK, MessageBoxIcon.Information);
                            Application.Restart();
                        }
                        catch (DeploymentDownloadException dde)
                        {
                            MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde,
                                "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                            return;
                        }
                    }
                }
                else
                {
                    this.Cursor = Cursors.Default;
                    MessageBox.Show("No updates are available at this time.\nPlease send feedback if there's a problem with the application from the 'About' dialogue.",
                        "Done", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            else
            {
                MessageBox.Show("This application was not deployed via ClickOnce.", "Impossible to Update", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutForm f = new AboutForm();
            f.ShowDialog();
        }

        private void resetQueryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Set default query
            ResetViewQueryToDefault(true);
        }

        private void loadFilesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenLogsDialogueAndStartLoading();
        }

        private void sendKudosToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SendKudos();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        #endregion

        #region Status Bar

        private void imgRealTimeMonitoringStatus_Click(object sender, EventArgs e)
        {
            OpenMonitoringDialogue();
        }

        private void btnCancelLoad_Click(object sender, EventArgs e)
        {
            SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Cancelling));

            ((FileSetJobLoadingStatus)this.AppEngine.UlsData.LoadStatuses[this.AppEngine.FileSetLoadSettings]).UserCancelledLoad = true;
        }

        #endregion

        private void btnApplyFilter_Click(object sender, EventArgs e)
        {
            ApplyUserFilter();
        }

        private void lstLogs_SelectionChanged(object sender, EventArgs e)
        {
            if (lstLogs.SelectedRows.Count == 1)
            {
                StartSelectClickedItem(lstLogs.SelectedRows[0].Index);
            }
        }

        private void lstLogs_Click(object sender, System.EventArgs e)
        {
            // Stop auto-scrolling
            Properties.Settings.Default.AutoScroll = false;
            Properties.Settings.Default.Save();
            btnAutoScroll.Checked = false;
        }

        #endregion

        #region TextBoxes

        // QuickSearch
        string originalSearchText = string.Empty;       // Populated on form load
        private void txtQuickSearch_Enter(object sender, EventArgs e)
        {
            if (txtQuickSearch.Text == originalSearchText)
            {
                txtQuickSearch.Text = string.Empty;
                this.txtQuickSearch.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
                this.txtQuickSearch.ForeColor = System.Drawing.SystemColors.WindowText;
            }
        }
        private void txtQuickSearch_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Enter)
            {
                NewQuickSearchWanted();
                e.Handled = true;
            }
        }
        private void txtQuickSearch_Leave(object sender, EventArgs e)
        {
            if (txtQuickSearch.Text.Trim().Length == 0)
            {
                // Reset to "Enter Quick Search Here"
                txtQuickSearch.Text = originalSearchText;
                this.txtQuickSearch.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
                this.txtQuickSearch.ForeColor = System.Drawing.Color.DarkGray;
            }
        }

        // Filter
        private void txtFilter_TextChanged(object sender, EventArgs e)
        {
            // Is/was query dirty?
            bool queryWasDirty = this.QueryIsDirty;
            this.QueryIsDirty = txtFilter.Text != this.LastGoodQueryText;

            // Raise event?
            if (!queryWasDirty && this.QueryIsDirty)
            {
                OnQueryIsDirty();
            }
            else if (queryWasDirty && !this.QueryIsDirty)
            {
                CheckNewQueryIsValid();
            }

            btnApplyFilter.Enabled = this.QueryIsDirty;
            applyQueryToolStripMenuItem.Enabled = btnApplyFilter.Enabled;
            ProcessQueryKeywordHighlighting();
        }


        // Event Message
        private void txtMessage_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.A & e.Modifiers == Keys.Control)
            {
                txtMessage.SelectAll();
                e.Handled = true;
                e.SuppressKeyPress = true;
            }
        }

        #endregion

        #endregion

        #region Custom Event Definitions

        #region UserQueryIsDirty

        // Query is dirty
        internal event EventHandler UserQueryIsDirty;
        private void OnQueryIsDirty()
        {
            if (UserQueryIsDirty != null)
            {
                UserQueryIsDirty(this, EventArgs.Empty);
            }
        }

        #endregion

        #region NewFilterBeingSet

        // New filter being set - can cancel new filter if event is trapped
        internal class NewFilterBeingSetEventArgs : EventArgs
        {
            public bool Cancel { get; set; }
            public NewFilterBeingSetEventArgs(bool cancel)
            {
                this.Cancel = cancel;
            }
        }
        internal event EventHandler<NewFilterBeingSetEventArgs> NewFilterBeingSet;
        private bool OnNewFilterBeingSet()
        {
            if (NewFilterBeingSet != null)
            {
                NewFilterBeingSetEventArgs e = new NewFilterBeingSetEventArgs(false);
                NewFilterBeingSet(this, e);
                return e.Cancel;
            }
            else
            {
                return true;        // No events hooked up; nothing cancelled
            }
        }

        #endregion

        #region NewFilterSet
        internal class NewFilterSetEventArgs : EventArgs
        {
            public NewFilterSetEventArgs(SimpleSqlSelectStatementInfo statement) { this.NewQuery = statement; }
            public SimpleSqlSelectStatementInfo NewQuery { get; set; }
        }

        /// <summary>
        /// A new filter has been validated and is now active
        /// </summary>
        internal event EventHandler<NewFilterSetEventArgs> NewFilterSet;
        private void OnNewFilterSet(SimpleSqlSelectStatementInfo statement)
        {
            if (NewFilterSet != null)
            {
                NewFilterSet(this, new NewFilterSetEventArgs(statement));
            }
        }

        #endregion

        #endregion


        // UI
        #region Drag & Drop
        private void Form_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop) == true)
            {
                e.Effect = DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }

        }

        private void Form_DragDrop(object sender, DragEventArgs e)
        {
            string[] formats = e.Data.GetFormats();
            object fileNamesObj = e.Data.GetData(DataFormats.FileDrop);
            if (fileNamesObj is string[])
            {
                List<string> draggedFileNames = new List<string>((string[])fileNamesObj);
                List<System.IO.FileInfo> fileNames = new List<System.IO.FileInfo>(draggedFileNames.Count);
                List<System.IO.DirectoryInfo> directoriesToInclude = new List<System.IO.DirectoryInfo>();

                // Find out what was dragged in
                foreach (string fileName in draggedFileNames)
                {
                    // Directory or file?
                    if (System.IO.Directory.Exists(fileName))
                    {
                        directoriesToInclude.Add(new System.IO.DirectoryInfo(fileName));
                    }
                    else if (System.IO.File.Exists(fileName))
                    {
                        System.IO.FileInfo draggedFile = new System.IO.FileInfo(fileName);
                        string ext = draggedFile.Extension.ToLower();
                        if (ext == ".log" || ext == ".txt")
                            fileNames.Add(draggedFile);
                    }
                }


                // Get settings
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, "Building file list..."));
                this.Cursor = Cursors.WaitCursor;
                Application.DoEvents();

                // Prepare new load job
                UlsScanSettings settings = UlsScanSettings.BuildSettingsFromDirectories(directoriesToInclude, true);
                settings.FilesToBeScanned.AddRange(fileNames);

                // Are there any valid files to load?
                if (settings.FilesToBeScanned.Count > 0)
                {
                    // Begin load
                    BeginLoadAllLogs(settings);
                }
                else
                {
                    // Set error
                    SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, "No files to load found (*.log/*.txt)"));
                }
                this.Cursor = Cursors.Default;
            }
        }

        #endregion
        
        #region Misc Display Methods

        /// <summary>
        /// Put the application in a mode (loading/not-loading)
        /// </summary>
        private void SetApplicationDisplay(DisplayStateUpdateInfo displayState)
        {
            // What's happening?
            bool haveCustomLoadingStatus = (displayState.BottomLeftLabel != null);
            bool realTimeWatcherRunning = this.AppEngine.LoadMode == LoadMode.RealTime && (
                displayState.MonitoringMode == MonitoringMode.Starting || displayState.MonitoringMode == MonitoringMode.Running);
            bool filesetLoading = this.AppEngine.LoadMode == LoadMode.FileSet && this.AppEngine.LoadInProgress;
            bool doingNothing = !(realTimeWatcherRunning | filesetLoading);
            bool nothingLoaded = this.LastUnfilteredRecordCount == 0;

            // Deal with RealTime Data
            if (displayState.MonitoringMode != MonitoringMode.NotSpecified)
            {
                imgRealTimeMonitoring.Visible = realTimeWatcherRunning;
            }
            if (displayState.MonitoringMode == MonitoringMode.Starting)
            {
                imgRealTimeMonitoring.Text = "Starting...";
                tmrPlayFlash.Enabled = false;
                return;
            }
            else if (displayState.MonitoringMode == MonitoringMode.Running)
            {
                string msg = "Monitoring {0} ULS locations";
                if (displayState.DirectoryWatchCount == 1)
                {
                    msg = "Monitoring 1 ULS location";
                }
                imgRealTimeMonitoring.Text = string.Format(msg, displayState.DirectoryWatchCount);
            }

            // Pulsing "Watching X ULS locations" timer
            tmrPlayFlash.Enabled = imgRealTimeMonitoring.Visible;

            // App mode
            btnCancelLoad.Visible = displayState.State == ApplicationState.Loading;
            progressBar1.Visible = btnCancelLoad.Visible;

            // Enable "refresh stats"?
            if (!this.RefreshStatsPressed)
            {
                btnRefreshStats.Enabled = displayState.State == ApplicationState.Loading;
            }

            // App mode specific code
            if (displayState.State == ApplicationState.Loading)
            {
                // Hide "bored" display
                pnlBored.Visible = false;

                if (!this.RefreshStatsPressed)
                {
                    // Set stat buttons as unknown if they've not been refreshed yet (btnRefreshStats.Enabled == false when they've been refreshed).
                    btnShowVerbose.Text = "(?)";
                    btnShowInfo.Text = "(?)";
                    btnShowWarning.Text = "(?)";
                    btnShowHigh.Text = "(?)";
                    btnShowCritical.Text = "(?)";
                    btnShowUnexpected.Text = "(?)";
                    btnShowException.Text = "(?)";
                }

                btnMonitorFarm.Enabled = false;     // Disable real-time ULS loading to avoid connection/thread contention
                btnSelectLoadSource.Enabled = false;        // Can't be loading x2 things at once

                // Use default msg? If a msg is specified, label is set at bottom of method
                if (!haveCustomLoadingStatus)
                {
                    //lblLoadingStatus.Text = "Parsing files...";   // Commented so label isn't changed from last change if no new value set
                }

                btnCancelLoad.Text = "Cancel";
                btnCancelLoad.Enabled = true;
                if (string.IsNullOrEmpty(displayState.BottomRightLabel))
                {
                    bottomRightToolStripLabel.Text = string.Format("Loading files...");
                }
                else
                {
                    bottomRightToolStripLabel.Text = displayState.BottomRightLabel;
                }

                this.Cursor = Cursors.AppStarting;

                // Disable drag & drop
                this.AllowDrop = false;
                lstLogs.AllowDrop = false;

                // Percent done
                if (displayState.PercentageComplete.HasValue)
                {
                    progressBar1.Value = displayState.PercentageComplete.Value;
                    SetWindowsIcon(true, displayState.PercentageComplete.Value);
                }
            }
            else if (displayState.State == ApplicationState.Waiting)
            {
                // Hide "bored" display?
                if (doingNothing && nothingLoaded)
                {
                    pnlBored.Visible = true;
                }
                else
                {
                    pnlBored.Visible = false;
                }

                // Enable load buttons?
                if (doingNothing)
                {
                    SetWindowsIcon(false);
                    btnMonitorFarm.Enabled = true;
                    btnSelectLoadSource.Enabled = true;
                }
                else
                {
                    // Files are loading only in "Loading" mode, not "Waiting"
                    if (realTimeWatcherRunning)
                    {
                        SetWindowsIcon(true, 100);
                        btnSelectLoadSource.Enabled = false;
                    }
                }

                this.RefreshStatsPressed = false;

                // Allow drag & drop
                this.AllowDrop = true;
                lstLogs.AllowDrop = true;

                this.Cursor = Cursors.Default;

                if (string.IsNullOrEmpty(displayState.BottomRightLabel))
                {
                    bottomRightToolStripLabel.Text = string.Empty;
                }
                else
                {
                    bottomRightToolStripLabel.Text = displayState.BottomRightLabel;
                }

            }
            else if (displayState.State == ApplicationState.Cancelling)
            {
                lblLoadingStatus.Text = "Cancelling...";
            }

            // Set user-defined loading status if there is one
            if (haveCustomLoadingStatus)
            {
                lblLoadingStatus.Text = displayState.BottomLeftLabel;
            }
        }

        #region Chorradas

        void SetFilterButtonsEnabled(bool enabled)
        {
            btnShowVerbose.Enabled = enabled;
            btnShowInfo.Enabled = enabled;
            btnShowWarning.Enabled = enabled;
            btnShowHigh.Enabled = enabled;
            btnShowUnexpected.Enabled = enabled;
            btnShowCritical.Enabled = enabled;
            btnShowException.Enabled = enabled;

            saveQueryToolStripMenuItem.Enabled = enabled;
            loadQueryToolStripMenuItem.Enabled = enabled;
            txtQuickSearch.Enabled = enabled;
        }

        /// <summary>
        /// Windows 7 stuff
        /// </summary>
        /// <param name="showLoading"></param>
        void SetWindowsIcon(bool showLoading)
        {
            SetWindowsIcon(showLoading, -1);
        }
        void SetWindowsIcon(bool showLoading, int percentageComplete)
        {
            bool win7orHigher = Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1;
            if (!win7orHigher)
            {
                return;
            }

            // Win7 GUI calls
            if (!showLoading)
            {
                Windows7.DesktopIntegration.Windows7Taskbar.SetProgressState(this.Handle, Windows7.DesktopIntegration.Windows7Taskbar.ThumbnailProgressState.NoProgress);
            }
            else
            {

                if (percentageComplete > -1)
                {
                    Windows7.DesktopIntegration.Windows7Taskbar.SetProgressState(this.Handle, Windows7.DesktopIntegration.Windows7Taskbar.ThumbnailProgressState.Normal);
                    Windows7.DesktopIntegration.Windows7Taskbar.SetProgressValue(this.Handle, (ulong)percentageComplete, 100);
                }
                else
                {
                    Windows7.DesktopIntegration.Windows7Taskbar.SetProgressState(this.Handle, Windows7.DesktopIntegration.Windows7Taskbar.ThumbnailProgressState.Indeterminate);
                    Windows7.DesktopIntegration.Windows7Taskbar.SetProgressValue(this.Handle, 100, 100);
                }
            }
        }

        /// <summary>
        /// Sets GUI for query change
        /// </summary>
        private bool CheckNewQueryIsValid()
        {
            SimpleSqlSelectStatementInfo newQuery = new SimpleSqlSelectStatementInfo(txtFilter.Text);

            // Set textbox background colour if view query is valid, or red if not valid
            if (newQuery.StatmentIsValid)
            {
                SetFilterTextBoxColour(FilterState.Normal);
            }
            else
            {
                SetFilterTextBoxColour(FilterState.Error);
            }


            return newQuery.StatmentIsValid;
        }

        /// <summary>
        /// Sets filter text background colour
        /// </summary>
        /// <param name="isError"></param>
        private void SetFilterTextBoxColour(FilterState isError)
        {
            Color green = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(255)))), ((int)(((byte)(230)))));
            Color red = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));

            if (isError == FilterState.Normal)
            {
                if (txtFilter.BackColor != green)
                    txtFilter.BackColor = green;
            }
            else if (isError == FilterState.Dirty)
            {
                Color yellow = System.Drawing.Color.FromArgb((int)(((byte)(255))), (int)(((byte)(255))), (int)(((byte)(220))));
                this.txtFilter.BackColor = yellow;
            }
            else if (isError == FilterState.Error)
            {
                if (txtFilter.BackColor != red)
                    txtFilter.BackColor = red;
            }
        }

        #endregion

        /// <summary>
        /// Send me Kudos, make me happy
        /// </summary>
        private void SendKudos()
        {
            // CorpNet check
            this.Cursor = Cursors.WaitCursor;
            bool corpConnected = this.AppEngine.ClientIsConnectedToCorpNet();
            this.Cursor = Cursors.Default;

            // If we're on CorpNet, use the internal app
            if (corpConnected)
            {
                System.Diagnostics.Process.Start("http://kudos/SendKudos.aspx?alias=sambetts");
            }
            else
            {
                // Ask permission first
                string question =
                    "Few things make me happier than getting an email saying 'Thanks!' from a stranger." + Environment.NewLine +Environment.NewLine + 
                    "Would it be ok to draft up one so you can send it?";
                bool okToSend = (
                    MessageBox.Show(question, "Send Kudos", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                ) == System.Windows.Forms.DialogResult.Yes;

                if (okToSend)
                {
                    string body = "Hi Sam - Thanks for sharing the SharePoint tool!";
                    try
                    {
                        // Hand off request to OS
                        System.Diagnostics.Process.Start("mailto:sambetts@microsoft.com?Subject=ULS Studio Rocks!&body=" + body);
                    }
                    catch (Win32Exception)
                    {
                        // Email open failed
                        string errBody =
                            "Oops, that didn't work, probably because there's no email application installed." + Environment.NewLine + Environment.NewLine +
                            "If you get time please just send me an email as you see fit - sambetts@microsoft.com - it's always truly appreciated!";

                        MessageBox.Show(errBody, "Send Kudos", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    }
                    
                }
            }
            
        }

        #endregion

        #region Syntax Highlighting

        /// <summary>
        /// Sets a set of text a particular colour
        /// </summary>
        private void HighlightWord(int wordIndex, int wordLength, Color foregroundColour)
        {
            txtFilter.Select(wordIndex, wordLength);
            if (txtFilter.SelectionColor != foregroundColour)
            {
                Logging.Writeline("MainForm.HighlightWord - Highlighted word '{0}' {1}", txtFilter.SelectedText, foregroundColour);
                txtFilter.SelectionColor = foregroundColour;
            }
        }

        bool setQueryTextColourBlack = true;
        private void ProcessQueryKeywordHighlighting()
        {
            ControlBeginDrawUpdate(txtFilter);

            // Remember where cursor is
            int cursorStartPosition = txtFilter.SelectionStart, lastUnHighlightedWordStartIdx = -1;

            // Make everything black, like my soul
            if (setQueryTextColourBlack | txtFilter.Text.Length < 2)   // This can happen is CTRL+A then a key is pressed to overwrite selection (length == 1)
            {
                txtFilter.Select(0, txtFilter.Text.Length);
                txtFilter.SelectionColor = Color.Black;
                setQueryTextColourBlack = false;
            }
            else
            {
                // We're not setting everything black

                // Was a already highlighted keyword being changes?
                if (txtFilter.SelectionColor == Color.Blue)
                {
                    // Rememeber this so highlighting code doesn't re-highlight
                    lastUnHighlightedWordStartIdx = cursorStartPosition;

                    bool moreWordOnLeft = true;
                    while (moreWordOnLeft)
                    {
                        if (txtFilter.SelectionStart > 0)
                        {
                            txtFilter.SelectionStart -= 1;
                            moreWordOnLeft = txtFilter.Text.Substring(txtFilter.SelectionStart, 1) != " ";
                        }
                        else
                        {
                            moreWordOnLeft = false;
                        }
                    }

                    bool moreWordOnRight = true;
                    while (moreWordOnRight)
                    {
                        if (txtFilter.SelectionStart + txtFilter.SelectionLength < txtFilter.Text.Length - 1)       // -1 added because if the length was the same as selection, it would crash
                        {
                            txtFilter.SelectionLength++;
                            moreWordOnRight = txtFilter.Text.Substring(txtFilter.SelectionStart + txtFilter.SelectionLength, 1) != " ";
                        }
                        else
                        {
                            moreWordOnRight = false;
                        }
                    }

                    Logging.Writeline("MainForm.ProcessQueryKeywordHighlighting - Un-highlighted '{0}'", txtFilter.SelectedText);
                    txtFilter.SelectionColor = Color.Black;
                }
            }

            // Find current keywords - make blue if they aren't already
            System.Text.RegularExpressions.Regex keyWordsRegEx = new System.Text.RegularExpressions.Regex("select | distinct | from |where ");
            foreach (System.Text.RegularExpressions.Match match in keyWordsRegEx.Matches(txtFilter.Text))
            {
                if (match.Index != lastUnHighlightedWordStartIdx)
                    HighlightWord(match.Index, match.Length, Color.Blue);
            }

            // Find OPERATOR word - make grey
            System.Text.RegularExpressions.Regex operatorWordsRegEx = new System.Text.RegularExpressions.Regex(" and | or ");
            foreach (System.Text.RegularExpressions.Match match in operatorWordsRegEx.Matches(txtFilter.Text))
            {
                if (match.Index != lastUnHighlightedWordStartIdx)
                    HighlightWord(match.Index, match.Length, Color.Gray);
            }

            // Grab quoted words - make red
            System.Text.RegularExpressions.Regex quotedWordsRegEx = new System.Text.RegularExpressions.Regex("'(.*?)'");
            foreach (System.Text.RegularExpressions.Match match in quotedWordsRegEx.Matches(txtFilter.Text))
            {
                HighlightWord(match.Index, match.Length, Color.DarkRed);
            }

            // Reset cursor
            txtFilter.SelectionStart = cursorStartPosition;
            txtFilter.SelectionLength = 0;
            txtFilter.SelectionColor = Color.Black;

            ControlEndDrawUpdate(txtFilter);
            txtFilter.Refresh();

        }

        /// <summary>
        /// Freezes paint methods to 
        /// </summary>
        void ControlBeginDrawUpdate(Control controlToFreeze)
        {
            SendMessage(controlToFreeze.Handle, WM_SETREDRAW, (IntPtr)0, IntPtr.Zero);
        }
        void ControlEndDrawUpdate(Control frozenControl)
        {
            SendMessage(frozenControl.Handle, WM_SETREDRAW, (IntPtr)1, IntPtr.Zero);
        }

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
        private const int WM_SETREDRAW = 0x0b;

        #endregion


        // Loading file/real-time data
        #region File Load Methods

        /// <summary>
        /// Opens load dialogue and involks load if all ok
        /// </summary>
        private void OpenLogsDialogueAndStartLoading()
        {
            // Sanity
            if (this.QueryIsDirty)
            {
                MessageBox.Show("Please finish setting your display query then try again.", "Display Query", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            // Open form for files & dir mode
            SelectDatasourceForm f = new SelectDatasourceForm();
            f.FormMode = DatasourceSelectMode.FilesAndDirectories;

            // Show Load dialogue
            if (f.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                UlsScanSettings settings = null;
                if (f.SelectedDirectory != null)
                {
                    DirectoryBuilderForm dirForm = new DirectoryBuilderForm();
                    dirForm.BaseDir = f.SelectedDirectory;
                    dirForm.ShowDialog();
                    settings = dirForm.Settings;
                }
                else if (f.FilesSelected != null)
                {
                    settings = new UlsScanSettings(f.FilesSelected);
                }
                else
                {
                    MessageBox.Show("Unsure what was selected", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }

                // If settings OK, start load
                if (settings != null && settings.SettingsAreValid)
                {
                    BeginLoadAllLogs(settings);
                }
            }
        }

        /// <summary>
        /// Starts loading
        /// </summary>
        private void BeginLoadAllLogs(UlsScanSettings loadSettings)
        {
            // Check initial query
            if (!this.AppEngine.CurrentQuery.StatmentIsValid)
            {
                MessageBox.Show("Cannot continue - query is invalid", "Load Logs", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                return;
            }

            Logging.Writeline("MainForm.BeginLoadLogs(): Begin loading process...");
            btnAutoScroll.Checked = false;      // Disable auto-scroll for file-load
            lastLoadStatusUpdate = DateTime.MinValue;

            // Setup screen for new dataset
            ClearDisplayCache(true);
            RebuildListColumns();

            // Begin background load
            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(this.AppEngine.UlsData.AppendLogFiles));
            t.Name = "File Loader Thread";
            t.Start(loadSettings);

            // Remember last settings for UI updates during the load
            this.AppEngine.FileSetLoadSettings = loadSettings;
            this.AppEngine.LoadMode = LoadMode.FileSet;

            // Set GUI
            this.AppEngine.LastLoadingStatus = new AwaitingDataLoadingStatus();
            this.AppEngine.GridHelper.LastSelectedID = 0;
            SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Loading, "Loading", 0));

            Logging.Writeline("MainForm.BeginLoadLogs(): Loading setup.");
        }

        /// <summary>
        /// Finished loading
        /// </summary>
        private void DoneLoadingLogs()
        {
            // Display
            SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting));
        }

        #endregion

        #region Real-Time ULS Load Methods

        void OpenMonitoringDialogue()
        {
            // Sanity
            if (this.QueryIsDirty)
            {
                MessageBox.Show("Please finish setting your display query then try again.", "Display Query", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            StartFarmMonitoringForm f = new StartFarmMonitoringForm();
            f.MainForm = this;
            if (f.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                this.AppEngine.RealTimeSettings.Directories = f.SelectedDirs;
                StartMonitoring(f.SelectedDirs.Count);
            }
        }

        /// <summary>
        /// Begin monitoring for ULS changes in real-time
        /// </summary>
        /// <param name="locationCount"></param>
        private void StartMonitoring(int locationCount)
        {
            this.Cursor = Cursors.WaitCursor;

            // Start GUI in monitoring mode
            SetApplicationDisplay(new DisplayStateUpdateInfo(MonitoringMode.Starting, locationCount));     // Wait for thread event to confirm when running
            Application.DoEvents();

            // Start monitoring
            this.AppEngine.UlsData.StartWatching(this.AppEngine.RealTimeSettings);

            btnAutoScroll.Checked = true;       // Auto-scroll by default

            pnlBored.Visible = false;
            this.AppEngine.LoadMode = LoadMode.RealTime;
            SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, "Awaiting real-time data..."));       // Waiting for real-time data
            this.Cursor = Cursors.Default;
        }

        void UlsData_WatcherStopped(object sender, EventArgs e)
        {
            // Do stuff on main thread, assuming this stop isn't because of an app shutdown
            if (this.IsDisposed)
            {
                return;
            }

            // Update GUI on mainthread
            if (InvokeRequired)
            {
                Invoke(new Action<object, EventArgs>(UlsData_WatcherStopped), sender, e);
                return;
            };

            SetApplicationDisplay(new DisplayStateUpdateInfo(MonitoringMode.NotRunning, 0));
        }

        internal enum TBPFLAG
        {
            TBPF_NOPROGRESS = 0,
            TBPF_INDETERMINATE = 0x1,
            TBPF_NORMAL = 0x2,
            TBPF_ERROR = 0x4,
            TBPF_PAUSED = 0x8
        }

        void UlsData_WatcherStarted(object sender, WatcherStartedEventArgs e)
        {
            // Update GUI on mainthread
            if (InvokeRequired)
            {
                Invoke(new Action<object, WatcherStartedEventArgs>(UlsData_WatcherStarted), sender, e);
                return;
            };

            // Do stuff on main thread
            SetApplicationDisplay(new DisplayStateUpdateInfo(MonitoringMode.Running, e.DirectoryTargets.Count));
        }

        private void tmrPlayFlash_Tick(object sender, EventArgs e)
        {
            Color colour1 = SystemColors.ControlText;
            Color colour2 = System.Drawing.Color.DarkRed;
            if (imgRealTimeMonitoring.ForeColor == colour1)
            {
                imgRealTimeMonitoring.ForeColor = colour2;
            }
            else
            {
                imgRealTimeMonitoring.ForeColor = colour1;
            }
        }

        #endregion

        #region Stats & Loading Updates

        DateTime lastLoadStatusUpdate = DateTime.MinValue;
        /// <summary>
        /// There's a new loading status. Can mean more data or finished
        /// </summary>
        private void NewLoadingStatus(AbstractLoadingStatus loadStatus)
        {
            // Do stuff on main thread, assuming this isn't an app shutdown
            if (this.IsDisposed) { return; }

            // Update GUI on mainthread
            if (InvokeRequired)
            {
                try
                {
                    Invoke(new Action<AbstractLoadingStatus>(NewLoadingStatus), loadStatus);
                }
                catch (ObjectDisposedException)
                {
                    // Ignore
                }

                return;
            };

            Logging.Writeline("MainForm.MoreDataAvailable() called; load @ {0}% complete.", loadStatus.PercentageComplete);
            if (this.IsDisposed)
            {
                Logging.Writeline("Ignoring update - application quitting....");
                return;
            }

            // Remember load stats (for EndUpdateRecordStats)
            this.AppEngine.LastLoadingStatus = loadStatus;

            // Update GUI to "loading"/"loaded" until full stats come back
            if (loadStatus.LoadComplete)
            {
                // Allow file-loading again if it was disabled & RTULS isn't running
                btnMonitorFarm.Enabled = true;

                if (this.AppEngine.UlsData.HaveInsertedData)
                {
                    // Also calls SetDisplayMode(new DisplayStateUpdate(AppState.Waiting));
                    BeginUpdateRecordStats(true, ApplicationState.Waiting);     
                }
                else
                {
                    // Nothing ULS was loaded - don't thread-load a stats job, set it now directly.
                    ProcessNewStats(FullDataStatistics.Empty);
                }

                // Update stats & grid
                lastLoadStatusUpdate = DateTime.Now;

                Logging.Writeline("MainForm.MoreDataAvailable() Load is complete. Now awaiting final grid-count & stats.\n");
            }
            else
            {
                // When was the last update?
                int secondsSinceLastUpdate = DateTime.Now.Subtract(lastLoadStatusUpdate).Seconds;
                if (secondsSinceLastUpdate > 5)
                {
                    BeginUpdateRecordStats(false, ApplicationState.Loading);
                }

                // What type of update is it?
                if (loadStatus is FileSetJobLoadingStatus)
                {
                    string currentFileMsg = string.Format("Loading '{0}' ({1:N0} KB)...",
                        ((FileSetJobLoadingStatus)loadStatus).CurrentFile.Name,
                        ((FileSetJobLoadingStatus)loadStatus).CurrentFile.Length / 1024);

                    bool userHasCancelledLoad = ((FileSetJobLoadingStatus)loadStatus).UserCancelledLoad;
                    if (!userHasCancelledLoad)
                    {
                        SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Loading, currentFileMsg, this.LastRecordLoadedStats, loadStatus.PercentageComplete));
                    }

                }
                else
                {
                    SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Loading, "Loading..."));
                }

                // Set vars for GUI update
                int secondsSinceLastScreenRefresh = DateTime.Now.Subtract(lastDisplayUpdate).Seconds;
                if (secondsSinceLastScreenRefresh >= Constants.UPDATE_SECONDS)
                {
                    lastDisplayUpdate = DateTime.Now;
                }
            }

        }

        /// <summary>
        /// Processes new log statistics
        /// </summary>
        /// <param name="baseDataStats">Stats received</param>
        private void ProcessNewStats(BaseDataStats statsJustLoaded)
        {
            lastLoadStatusUpdate = DateTime.Now;

            this.LastUnfilteredRecordCount = statsJustLoaded.TotalRecordsInserted;

            // Update button labels
            if (statsJustLoaded is FullDataStatistics)
            {
                FullDataStatistics fullStats = (FullDataStatistics)statsJustLoaded;
                string btnMask = "({0})";
                btnShowVerbose.Text = string.Format(btnMask, fullStats.VerboseEntries);
                btnShowInfo.Text = string.Format(btnMask, fullStats.InfoEntries);
                btnShowWarning.Text = string.Format(btnMask, fullStats.WarningEntries);
                btnShowHigh.Text = string.Format(btnMask, fullStats.HighEntries);
                btnShowCritical.Text = string.Format(btnMask, fullStats.CriticalEntries);
                btnShowUnexpected.Text = string.Format(btnMask, fullStats.UnexpectedEntries);
                btnShowException.Text = string.Format(btnMask, fullStats.Exceptions);

                // If
            }

            // Start grid redrawing again
            SetGridVirtualSize(statsJustLoaded.TotalRecordsShowByFilter);
            AutoScrollIfNecesary();

            // Try & find last selected id? (set in MainForm_NewQuery)
            if (this.WaitForLastSelectedEntry)
            {
                // Load the record from the display cache
                Logging.Writeline("---Starting cache-fill to get last selected ID '{0}'", this.AppEngine.GridHelper.LastSelectedID.Value);
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, "Finding last selected entry..."));
                Application.DoEvents();

                // Get record from cache
                DatabaseLoadedUlsLogEntry lastClickedEntry = this.AppEngine.GridHelper.GetLogEntryByIdFromDisplayCache(this.AppEngine.GridHelper.LastSelectedID.Value, this.AppEngine.CurrentQuery);

                // Goto it in the grid
                lstLogs.FirstDisplayedScrollingRowIndex = lastClickedEntry.ViewIndex;
                DataGridViewRow lastGridRow = lstLogs.Rows[lastClickedEntry.ViewIndex];
                lastGridRow.Selected = true;

                Logging.Writeline("MainForm.MainForm_NewQuery - Loaded last clicked entry.");
                // CheckIfNeedToSelectThisRecord(lastClickedEntry); // Has to be called by the grid "cell wanted" event, because right now there's nothing in the grid
            }


            if (!this.AppEngine.LoadInProgress)
            {
                Logging.Writeline("MainForm.ProcessNewStats - Dataset stats reloaded with stats type {0}", statsJustLoaded.GetType());

                // Grab load status
                AbstractLoadingStatus lastLoadingstatus = this.AppEngine.LastLoadingStatus;

                // Update record-count GUI
                decimal percentageShown = 0;
                string loadingMsg = string.Empty, recordsLoadedStats = string.Empty;

                // Nothing to update? This occurs at app start-up too.
                if (statsJustLoaded.TotalRecordsInserted == 0 & statsJustLoaded.TotalRecordsShowByFilter == 0)
                {
                    SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, "No ULS data found"));
                    return;
                }
                else
                {
                    // What type of update is this?
                    if (lastLoadingstatus is FileSetJobLoadingStatus)
                    {
                        FileSetJobLoadingStatus fileStatus = (FileSetJobLoadingStatus)lastLoadingstatus;

                        // Set GUI
                        loadingMsg = string.Format("{0:N0} entries loaded in {1} seconds from {2:N0} KB",
                            statsJustLoaded.TotalRecordsInserted,
                            Math.Round(DateTime.Now.Subtract(lastLoadingstatus.LoadStartTime).TotalSeconds, 2),
                            (fileStatus.BytesProcessed / 1024));

                        // Does the current filter display anything? Was anything inserted even? Alert user
                        if (statsJustLoaded.TotalRecordsInserted == 0)
                        {
                            MessageBox.Show("There was no valid ULS data found - were those SharePoint ULS logs you tried to open?", 
                                "Load Complete", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        }
                        else
                        {
                            if (statsJustLoaded.TotalRecordsShowByFilter == 0)
                            {
                                MessageBox.Show("ULS data loaded but your current filter has excluded all entries.",
                                "Load Complete", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                            }
                        }
                    }
                    else if (lastLoadingstatus is RealTimeLoadingStatus)
                    {
                        // Show how many entries were inserted from real-time data
                        string entWord = "entries";
                        RealTimeLoadingStatus s = (RealTimeLoadingStatus)lastLoadingstatus;
                        if (s.RowsInserted == 1)
                        {
                            entWord = "entry";
                        }
                        loadingMsg = string.Format("Last update at {0} - {1} new real-time {2} inserted",
                            DateTime.Now.ToLongTimeString(),
                               s.RowsInserted,
                               entWord);
                    }
                }
                // Show % of records found
                if (statsJustLoaded.TotalRecordsInserted > 0)
                {
                    double d = (Convert.ToDouble(statsJustLoaded.TotalRecordsShowByFilter) / Convert.ToDouble(statsJustLoaded.TotalRecordsInserted)) * 100;
                    percentageShown = Convert.ToDecimal(Math.Round(d, 2));

                    if (percentageShown == 100)
                    {
                        recordsLoadedStats = string.Format("All {1:N0} entries shown", statsJustLoaded.TotalRecordsShowByFilter, statsJustLoaded.TotalRecordsInserted);
                    }
                    else
                    {
                        recordsLoadedStats = string.Format("{0:N0}/{1:N0} entries shown ({2:N2}%)", statsJustLoaded.TotalRecordsShowByFilter, statsJustLoaded.TotalRecordsInserted, percentageShown);
                    }
                }

                // Set display
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, loadingMsg, recordsLoadedStats));
            }
            else        // this.AppEngine.LoadInProgress
            {
                // Still loading...
                this.LastRecordLoadedStats = string.Format("{0:N0} ULS entries loaded...", statsJustLoaded.TotalRecordsInserted);
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Loading, null, this.LastRecordLoadedStats));      // Pass emptystring for bottom-left so lbl isn't changed
                Logging.Writeline("MainForm.ProcessNewStats (LoadInProgress) - Dataset stats reloaded with stats type {0}", statsJustLoaded.GetType());
            }
        }


        /// <summary>
        /// Sets the grid size, based on records that should be shown
        /// </summary>
        private void SetGridVirtualSize(int totalRecords)
        {
            Logging.Writeline("MainForm.SetGridVirtualSize - Setting grid virtual-size to {0}", totalRecords);
            lastDisplayEntry = null;

            if (totalRecords > 0)
            {
                lstLogs.RowCount = totalRecords;
            }
        }

        /// <summary>
        /// Updates toolbar stats
        /// </summary>
        private void BeginUpdateRecordStats(bool fullUpdate, ApplicationState state)
        {
            Logging.Writeline("MainForm.BeginUpdateRecordStats - BeginUpdateRecordStats() called");
            if (fullUpdate)
            {
                // Set GUI
                SetApplicationDisplay(new DisplayStateUpdateInfo(state, "Calculating statistics (full)..."));
            }
            else
            {
                // Only set new display mode once loaded everything
                bool cancelling = false;
                if (this.AppEngine.LastLoadingStatus is FileSetJobLoadingStatus)
                {
                    FileSetJobLoadingStatus s = (FileSetJobLoadingStatus)this.AppEngine.LastLoadingStatus;
                    cancelling = s.UserCancelledLoad;
                }

                // Set GUI
                if (!this.AppEngine.LoadInProgress && !cancelling)
                {
                    SetApplicationDisplay(new DisplayStateUpdateInfo(state, "Calculating filter/total records..."));
                }
            }

            // Launch new thread
            System.Threading.Thread statsThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(this.AppEngine.UpdateRecordStats));
            statsThread.Name = "Stats Thread";
            statsThread.Start(fullUpdate);
        }

        #endregion


        // Dataset navigation
        #region Grid Data & Caching Methods

        int lastCol = 0, lastRow = 0; DatabaseLoadedUlsLogEntry lastDisplayEntry = null; string lastCellVal = string.Empty;

        #region Grid Selected Item Loading

        /// <summary>
        /// Grid item clicked - start load
        /// </summary>
        /// <param name="listIndex"></param>
        private void StartSelectClickedItem(int listIndex)
        {
            if (listIndex > -1)
            {
                DataLoadInfo inf = new DataLoadInfo(listIndex, this.AppEngine.CurrentQuery);
                System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(this.AppEngine.GridHelper.LoadClickedItem));
                t.Start(inf);
            }
        }

        #endregion

        private void lstLogs_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
        {
            bool needToLookupWantedCellValue = false; int lastSelectedViewIndex = -1;

            // Get right record
            if (lastDisplayEntry == null || lastRow != e.RowIndex)
            {
                // If this is a different record, get it
                lastRow = e.RowIndex;
                needToLookupWantedCellValue = true;

                try
                {
                    // Get entry from cache (add to cache first if necesary)
                    lastDisplayEntry = this.AppEngine.GridHelper.GetLogEntryFromDisplayCache(new DataLoadInfo(e.RowIndex, this.AppEngine.CurrentQuery));

                    // Is this record one that was last selected?
                    lastSelectedViewIndex = CheckIfNeedToSelectThisRecord(lastDisplayEntry);

                    // Debug
                    if (this.EnableGridConsoleOut)
                    {
                        Logging.Writeline("lstLogs_CellValueNeeded new record taken from cache with ID {0} & msg '{1}'",
                            lastDisplayEntry.DatabaseID, lastDisplayEntry.Message);
                    }
                }
                catch (ApplicationException)
                {
                    // Item was cleared from cache after being loaded (it wasn't there at the end of the method), probably because of a filter update
                    e.Value = string.Empty;
                    return;
                }

                // Paint rows
                SettingsCache settings = SettingsCache.GetSettingsCache();
                if (settings.ColoursEnabled)
                {
                    if (lastDisplayEntry.Level.ToLower() == "critical")
                    {
                        lstLogs.Rows[e.RowIndex].DefaultCellStyle.BackColor = settings.LineColourCriticalBack;
                        lstLogs.Rows[e.RowIndex].DefaultCellStyle.ForeColor = settings.LineColourCriticalFore;
                    }
                    else if (lastDisplayEntry.Level.ToLower() == "unexpected")
                    {
                        lstLogs.Rows[e.RowIndex].DefaultCellStyle.BackColor = settings.LineColourUnexpectedBack;
                        lstLogs.Rows[e.RowIndex].DefaultCellStyle.ForeColor = settings.LineColourUnexpectedFore;
                    }
                    else if (lastDisplayEntry.Level.ToLower() == "exception")
                    {
                        lstLogs.Rows[e.RowIndex].DefaultCellStyle.BackColor = settings.LineColourExceptionBack;
                        lstLogs.Rows[e.RowIndex].DefaultCellStyle.ForeColor = settings.LineColourExceptionFore;
                    }
                }

            }

            // Is the column the same as last request?
            if (lastCol != e.ColumnIndex)
            {
                needToLookupWantedCellValue = true;
                lastCol = e.ColumnIndex;
            }

            // Do we need to find the column value for the entry we now have loaded, or is the column & row the same as last time?
            if (needToLookupWantedCellValue)
            {
                // Pick out the cell wanted

                //Logging.Writeline("Wanted: col{0},row{1}", e.ColumnIndex, e.RowIndex);
                DataGridViewColumn selectedCol = lstLogs.Columns[e.ColumnIndex];
                if (selectedCol.Name == Constants.FIELD_NAME_CATEGORY) lastCellVal = lastDisplayEntry.Category;
                else if (selectedCol.Name == Constants.FIELD_NAME_CORRELATION && lastDisplayEntry.Correlation != Guid.Empty) lastCellVal = lastDisplayEntry.Correlation.ToString();
                else if (selectedCol.Name == Constants.FIELD_NAME_EVENTID) lastCellVal = lastDisplayEntry.EventID;
                else if (selectedCol.Name == Constants.FIELD_NAME_ID) lastCellVal = lastDisplayEntry.DatabaseID.ToString();
                else if (selectedCol.Name == Constants.FIELD_NAME_LEVEL) lastCellVal = lastDisplayEntry.Level;
                else if (selectedCol.Name == Constants.FIELD_NAME_MSG) lastCellVal = lastDisplayEntry.Message;
                else if (selectedCol.Name == Constants.FIELD_NAME_PROCESS) lastCellVal = lastDisplayEntry.Process;
                else if (selectedCol.Name == Constants.FIELD_NAME_PRODUCT) lastCellVal = lastDisplayEntry.Area;
                else if (selectedCol.Name == Constants.FIELD_NAME_ROWID) lastCellVal = lastDisplayEntry.ViewIndex.ToString();
                else if (selectedCol.Name == Constants.FIELD_NAME_SOURCE) lastCellVal = lastDisplayEntry.Source;
                else if (selectedCol.Name == Constants.FIELD_NAME_TID) lastCellVal = lastDisplayEntry.ThreadID;
                else if (selectedCol.Name == Constants.FIELD_NAME_TIMESTAMP) lastCellVal = lastDisplayEntry.DateTime.ToString();
                else
                    lastCellVal = string.Empty;
            }

            if (this.EnableGridConsoleOut)
            {
                Logging.Writeline("lstLogs_CellValueNeeded wanted {0}x{1}, needToLookupValue={4} lastDisplayEntry.DatabaseID={2}, sending result '{3}'",
                    e.ColumnIndex, e.RowIndex, lastDisplayEntry.DatabaseID, lastCellVal, needToLookupWantedCellValue);
            }
            e.Value = lastCellVal;

        }



        /// <summary>
        /// Checks if a ULS entry was the last one to be selected
        /// </summary>
        private int CheckIfNeedToSelectThisRecord(DatabaseLoadedUlsLogEntry entry)
        {
            // If this row is the last selected row (if there was a selected row), select it
            bool needToSelectArecord = this.WaitForLastSelectedEntry && this.AppEngine.GridHelper.LastSelectedID.HasValue;

            if (needToSelectArecord)
            {
                bool thisCacheItemIsTheLastSelectedItem = this.AppEngine.GridHelper.LastSelectedID.Value == entry.DatabaseID;
                if (thisCacheItemIsTheLastSelectedItem)
                {
                    Logging.Writeline("MainForm.CheckIfNeedToSelectThisRecord - Found last selected entry with db ID {0}", entry.DatabaseID);

                    if (lstLogs.RowCount > entry.ViewIndex)
                    {
                        // Set grid selected item as loaded ULS entry row ID, not db ID

                        DataGridViewRow lastGridRow = lstLogs.Rows[entry.ViewIndex];

                        lastGridRow.Selected = true;
                        lstLogs.FirstDisplayedScrollingRowIndex = entry.ViewIndex;


                        this.WaitForLastSelectedEntry = false;     // Only look for a row to select once

                        return entry.ViewIndex;
                    }
                    else
                    {
                        Logging.Writeline("MainForm.CheckIfNeedToSelectThisRecord - Found last selected record, but there's nothing in the grid");
                    }
                }
            }
            return -1;
        }

        private const int WM_SCROLL = 276; // Horizontal scroll
        private const int WM_VSCROLL = 277; // Vertical scroll
        private const int SB_LINEUP = 0; // Scrolls one line up
        private const int SB_LINELEFT = 0;// Scrolls one cell left
        private const int SB_LINEDOWN = 1; // Scrolls one line down
        private const int SB_LINERIGHT = 1;// Scrolls one cell right
        private const int SB_PAGEUP = 2; // Scrolls one page up
        private const int SB_PAGELEFT = 2;// Scrolls one page left
        private const int SB_PAGEDOWN = 3; // Scrolls one page down
        private const int SB_PAGERIGTH = 3; // Scrolls one page right
        private const int SB_PAGETOP = 6; // Scrolls to the upper left
        private const int SB_LEFT = 6; // Scrolls to the left
        private const int SB_PAGEBOTTOM = 7; // Scrolls to the upper right
        private const int SB_RIGHT = 7; // Scrolls to the right
        private const int SB_ENDSCROLL = 8; // Ends scroll



        /// <summary>
        /// Builds columns acording to query
        /// </summary>
        void RebuildListColumns()
        {
            ColumnSettings settings = ColumnSettings.DefaultSettings;

            lstLogs.Columns.Clear();
            foreach (string visibleField in this.AppEngine.CurrentQuery.FieldCollection)
            {
                DataGridViewColumn c = new DataGridViewColumn();
                c.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
                c.Name = visibleField;
                c.HeaderText = visibleField;
                c.Width = settings.GetWidth(visibleField, 100);
                c.CellTemplate = new DataGridViewTextBoxCell();
                lstLogs.Columns.Add(c);
            }


            // This will ensure the grid CellWanted will reload the 1st record
            lastDisplayEntry = null;

            Logging.Writeline("MainForm.RebuildListColumns() Columns rebuilt from ViewQueryInfo.FieldCollection");

            //lstLogs.Refresh();
        }

        /// <summary>
        /// Clears down objects for a new scan
        /// </summary>
        /// <param name="clearBackEndToo">Delete everything from database too</param>
        private void ClearDisplayCache(bool clearBackEndToo)
        {
            if (clearBackEndToo) { this.AppEngine.UlsData.ClearResults(); }
            SetGridVirtualSize(0);
            this.AppEngine.GridHelper.ResetCache();
        }

        #region AutoScrolling

        /// <summary>
        /// Autoscrolls if users has selected 'autoscroll'
        /// </summary>
        private void AutoScrollIfNecesary()
        {
            if (btnAutoScroll.Checked && lstLogs.RowCount > 0)
            {
                Logging.Writeline("MainForm.AutoScrollIfNecesary() - Autoscrolling.");
                SelectLastRecord();
            }
        }

        /// <summary>
        /// Moves list cursor to last entry
        /// </summary>
        private void SelectLastRecord()
        {
            if (this.AppEngine.GridHelper.ListViewCache.Values.Count > 0)
            {
                int lastIndex = lstLogs.Rows.Count - 1;
                DataGridViewRow lastRow = lstLogs.Rows[lastIndex];
                lastRow.Selected = true;
                lstLogs.FirstDisplayedScrollingRowIndex = lastIndex;
            }
        }

        #endregion

        #endregion

        #region Searching & Filtering

        #region Grid Searching

        /// <summary>
        /// Returns if a row has a search value in it
        /// </summary>
        bool CheckLine(DataGridViewRow row, string lowercaseSearchVal)
        {
            foreach (DataGridViewCell c in row.Cells)
            {
                if (c.Value is string && c.Value.ToString().ToLower().Contains(lowercaseSearchVal)) return true;
            }

            return false;
        }

        /// <summary>
        /// Searches for data in the grid from currently selected position
        /// </summary>
        /// <param name="newSearchSettings"></param>
        public void SearchInGrid(GridSearchSettings newSearchSettings)
        {
            // Update settings
            this.SearchSettings = newSearchSettings;

            if (lstLogs.RowCount > 0)
            {
                int startIdx = 0;
                if (lstLogs.SelectedRows.Count == 0)
                {
                    // Nothing selected, start from top/bottom
                    if (newSearchSettings.SearchDown) startIdx = 0;
                    else startIdx = lstLogs.RowCount;
                }
                else if (lstLogs.SelectedRows.Count == 1)
                {
                    if (newSearchSettings.SearchDown) startIdx = lstLogs.SelectedRows[0].Index + 1;           // Start with the next selected record
                    else startIdx = startIdx = lstLogs.SelectedRows[0].Index - 1;           // Start with the `previous selected record
                }
                else
                {
                    MessageBox.Show("Something odd happened");
                    return;
                }

                // Search
                string lowerCaseSearchTerm = newSearchSettings.SearchText.ToLower().Trim();

                // GUI
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, "Searching in grid..."));
                Application.DoEvents();

                bool itemFound = false;
                if (newSearchSettings.SearchDown)
                {

                    Console.Write("Searching in grid (down) for '{0}', rows {1} to {2}...", newSearchSettings.SearchText, startIdx, lstLogs.RowCount);
                    for (int i = startIdx; i < lstLogs.RowCount; i++)
                    {
                        DataGridViewRow row = lstLogs.Rows[i];
                        if (CheckLine(lstLogs.Rows[i], lowerCaseSearchTerm))
                        {
                            row.Selected = true;
                            lstLogs.FirstDisplayedScrollingRowIndex = row.Index;
                            itemFound = true;
                            break;
                        }
                    }
                    Logging.Writeline("done.");
                }
                else
                {
                    Console.Write("Searching in grid (up) for '{0}', rows {1} to {2}...", newSearchSettings.SearchText, startIdx, 0);
                    for (int i = startIdx; i > 0; i--)
                    {
                        DataGridViewRow row = lstLogs.Rows[i];
                        if (CheckLine(lstLogs.Rows[i], lowerCaseSearchTerm))
                        {
                            row.Selected = true;
                            lstLogs.FirstDisplayedScrollingRowIndex = row.Index;
                            itemFound = true;
                            break;
                        }
                    }
                    Logging.Writeline("done.");
                }

                // Reset GUI
                SetApplicationDisplay(new DisplayStateUpdateInfo(ApplicationState.Waiting, "Idle"));

                // Found anything?
                if (!itemFound)
                {
                    if (startIdx > 0)
                    {
                        if (MessageBox.Show("Search finished - nothing found! Search again from the top?", "Search in Grid",
                            MessageBoxButtons.YesNo,
                            MessageBoxIcon.Information)
                            == DialogResult.Yes)
                        {
                            // Set 1st row entry
                            lstLogs.Rows[0].Selected = true;
                            lstLogs.FirstDisplayedScrollingRowIndex = 0;

                            // Seearch again
                            SearchInGrid(newSearchSettings);
                        }
                    }
                    else
                    {
                        MessageBox.Show("Search finished - nothing found!", "Search in Grid", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }

                }
            }
            else
            {
                MessageBox.Show("No data to search!", "Search", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

        }

        /// <summary>
        /// Opens new grid-search form
        /// </summary>
        void NewGridSearch()
        {
            SearchInGridForm f = new SearchInGridForm();
            f.Settings = this.SearchSettings;
            f.Show(this);
        }

        #endregion

        /// <summary>
        /// Called when there's an answer to if the new query being set is valid or not.
        /// </summary>
        private void FilterValidationFinished(AppCore.Events.QueryValidationResultEventArgs e)
        {
            // We can filter again now
            SetFilterButtonsEnabled(true);
            this.Cursor = Cursors.Default;

            this.UserQueryBeingAppliedSoDontAskAgain = false;           // Reset this so user will be asked again if they're changing an already dirty query.
            Logging.Writeline("MainForm.AppEngine_FilterValidated statementWorked={0}", e.IsValid);
            if (e.IsValid)
            {
                // Assuming success, set this as the new query
                this.AppEngine.SetNewQuery(e.Query);

                // Process new display query
                ProcessNewDisplayQuery();
            }
            else
            {
                SetFilterTextBoxColour(FilterState.Error);
                lblLoadingStatus.Text = "Error parsing display query - please revise or reset the SQL (options menu).";
                string err = string.Format("There was an error applying your filter:\n\n{0}", e.ErrorMessage);
                MessageBox.Show(err, "Apply Display Query", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// A new query has been set and validated - do things.
        /// </summary>
        void ProcessNewDisplayQuery()
        {
            // Back/forward buttons
            btnBack.Enabled = this.AppEngine.QueryHistory.Count - this.AppEngine.CurrentQueryHistoryDepth > 1;
            btnForward.Enabled = this.AppEngine.CurrentQueryHistoryDepth > 0;

            // Update filter textbox if necesary
            bool queryHasChanged = txtFilter.Text.Trim() != this.AppEngine.CurrentQuery.ToString();
            if (queryHasChanged)
            {
                setQueryTextColourBlack = true;           // replacing text needs to reset black default colour
                txtFilter.Text = this.AppEngine.CurrentQuery.ToString(true);
            }

            // Misc GUI
            CheckIfFilterButtonCheckStatesNeedUpdating();
            txtMessage.Text = string.Empty;

            // Clear down current result-set & cache
            RebuildListColumns();
            ClearDisplayCache(false);       // Will also SetGridVirtualSize(0);

            // Raise internal events now there's a new query set
            OnNewFilterSet(this.AppEngine.CurrentQuery);

            // The new grid stats/size will then cause it to start asking for data....which is when the grid code will start section-loading with this new query

            SetFilterTextBoxColour(FilterState.Normal);
            this.LastGoodQueryText = this.AppEngine.CurrentQuery.ToString();

            // Query is dirty anymore
            this.QueryIsDirty = false;
            btnApplyFilter.Enabled = this.QueryIsDirty;

            applyQueryToolStripMenuItem.Enabled = btnApplyFilter.Enabled;

            // Get items for new query
            if (this.AppEngine.LoadInProgress)
            {
                BeginUpdateRecordStats(false, ApplicationState.Loading);
            }
            else
            {
                // Update stats if data has been inserted
                if (this.AppEngine.UlsData.HaveInsertedData)
                {
                    BeginUpdateRecordStats(false, ApplicationState.Waiting);
                }
                else
                {
                    // No data inserted & not loading anything. Probably app-start.
                }
            }

            // Save query
            Properties.Settings.Default.LastQuery = this.AppEngine.CurrentQuery.ToString();
            Properties.Settings.Default.Save();

            // Was there a selected item?
            if (this.AppEngine.GridHelper.LastSelectedID.HasValue)
            {
                // Will the last selected ID be in the results for new query?
                this.WaitForLastSelectedEntry = this.AppEngine.UlsData.ResultHaveId(this.AppEngine.CurrentQuery, this.AppEngine.GridHelper.LastSelectedID.Value);
            }
            else
            {
                this.WaitForLastSelectedEntry = false;
            }
        }

        /// <summary>
        /// Applies whatever query is in txtFilter.Text
        /// </summary>
        private void ApplyUserFilter()
        {
            SimpleSqlSelectStatementInfo typedStatement = new SimpleSqlSelectStatementInfo(txtFilter.Text);
            if (!typedStatement.StatmentIsValid)
            {
                MessageBox.Show("That statement doesn't appear to be valid - are you selecting valid fields from the 'log' table?", "Query Validation", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            BeginSetDisplayFilter(typedStatement, false);
        }

        /// <summary>
        /// Threaded version of BeginSetDisplayFilter(bool) on the AppEngine object
        /// </summary>
        private void SetDisplayFilter(object newDisplayFilter)
        {
            this.AppEngine.SetDisplayFilter((SimpleSqlSelectStatementInfo)newDisplayFilter);
        }

        /// <summary>
        /// Set a SQL statement that will dictate how the grid works
        /// </summary>
        /// <param name="dirtyCheck">Check whether unsaved query changes have been applied first</param>
        /// <param name="newDisplayFilter">The new query to set as the filter</param>
        private void BeginSetDisplayFilter(SimpleSqlSelectStatementInfo newDisplayFilter, bool dirtyCheck)
        {
            Logging.Writeline("MainForm.BeginSetDisplayFilter - New query wanted '{0}'", newDisplayFilter);

            // Raise event
            if (dirtyCheck)
            {
                // Dirty check
                bool cancelNewFilter = OnNewFilterBeingSet();

                // Query dirty?
                if (cancelNewFilter)
                {
                    // Nothing
                    Logging.Writeline("MainForm.BeginSetDisplayFilter - New query cancelled in event.");
                    return;
                }
            }

            this.Cursor = Cursors.AppStarting;
            SetFilterButtonsEnabled(false);


            // Begin background load
            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(SetDisplayFilter));
            t.Name = "File Loader Thread";
            t.Start(newDisplayFilter);
        }

        #region List Right-click Column Filtering

        private string MENU_HIDE_ORIGINAL_TEXT = string.Empty, MENU_ONLYSHOW_ORIGINAL_TEXT = string.Empty;
        private string lastClickedColumnName = string.Empty, lastClickedValue = string.Empty;
        private void lstLogs_MouseClick(object sender, MouseEventArgs e)
        {
            const string DISABLED_TEXT = "(Auto-filter disabled for this column)";

            // What was clicked?
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                System.Windows.Forms.DataGridView.HitTestInfo inf = lstLogs.HitTest(e.X, e.Y);

                if (inf.RowIndex > -1)
                {
                    DataGridViewRow selectedRow = lstLogs.Rows[inf.RowIndex];


                    // Add dynamic filter
                    lastClickedColumnName = lstLogs.Columns[inf.ColumnIndex].Name;

                    // Enable menus?
                    bool clickedOnFilterableColumn = inf != null && !string.IsNullOrEmpty(lastClickedColumnName) && lastClickedColumnName != Constants.FIELD_NAME_MSG;
                    if (clickedOnFilterableColumn)
                    {
                        lastClickedValue = selectedRow.Cells[inf.ColumnIndex].Value.ToString();

                        // Update menu labels
                        hideWhenItemEqualsXToolStripMenuItem.Text = string.Format(MENU_HIDE_ORIGINAL_TEXT, lastClickedColumnName, lastClickedValue);
                        hideWhenItemEqualsXToolStripMenuItem.Enabled = true;

                        onlyShowItemsWhen01ToolStripMenuItem.Text = string.Format(MENU_ONLYSHOW_ORIGINAL_TEXT, lastClickedColumnName, lastClickedValue);
                        onlyShowItemsWhen01ToolStripMenuItem.Enabled = true;
                        clearFilterAndOnlyShowToolStripMenuItem.Enabled = true;
                    }
                    else
                    {
                        // Not a valid column; disable filter menus
                        hideWhenItemEqualsXToolStripMenuItem.Text = DISABLED_TEXT;
                        hideWhenItemEqualsXToolStripMenuItem.Enabled = false;
                        clearFilterAndOnlyShowToolStripMenuItem.Enabled = false;

                        onlyShowItemsWhen01ToolStripMenuItem.Text = DISABLED_TEXT;
                        onlyShowItemsWhen01ToolStripMenuItem.Enabled = false;
                    }

                    // Show context menu
                    mnuLogsRightClick.Show(lstLogs, e.X, e.Y);
                    selectedRow.Selected = true;
                }

            }
        }

        private void onlyShowItemsWhen01ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (this.QueryIsDirty)
            {
                ApplyUserFilter();
            }

            // Get current query
            SimpleSqlSelectStatementInfo newQuery = this.AppEngine.CurrentQuery;

            string fullClauseText = string.Format("{0} == '{1}'", lastClickedColumnName, lastClickedValue);

            // Find if there's a where clause for message
            WhereClause qsClause = new WhereClause(fullClauseText);
            newQuery.WhereClauses.Add(qsClause, FollowingOperatorType.And);


            // Set new grid display
            BeginSetDisplayFilter(newQuery, true);
        }

        private void hideWhenItemEqualsXToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Get current query
            SimpleSqlSelectStatementInfo newQuery = this.AppEngine.CurrentQuery;

            string fullClauseText = string.Format("{0} != '{1}'", lastClickedColumnName, lastClickedValue);

            // Find if there's a where clause for message
            WhereClause qsClause = new WhereClause(fullClauseText);
            newQuery.WhereClauses.Add(qsClause, FollowingOperatorType.And);


            // Set new grid display
            BeginSetDisplayFilter(newQuery, true);
        }

        private void clearFilterAndOnlyShowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (this.QueryIsDirty)
            {
                ApplyUserFilter();
            }

            // Get current query
            SimpleSqlSelectStatementInfo newQuery = this.AppEngine.CurrentQuery;
            newQuery.WhereClauses.Clear();
            string fullClauseText = string.Format("{0} == '{1}'", lastClickedColumnName, lastClickedValue);

            // Find if there's a where clause for message
            WhereClause qsClause = new WhereClause(fullClauseText);
            newQuery.WhereClauses.Add(qsClause, FollowingOperatorType.And);


            // Set new grid display
            BeginSetDisplayFilter(newQuery, true);
        }

        private void clearFilterAndHideEntriesWhenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Get current query
            SimpleSqlSelectStatementInfo newQuery = this.AppEngine.CurrentQuery;
            newQuery.WhereClauses.Clear();
            string fullClauseText = string.Format("{0} != '{1}'", lastClickedColumnName, lastClickedValue);

            // Find if there's a where clause for message
            WhereClause qsClause = new WhereClause(fullClauseText);
            newQuery.WhereClauses.Add(qsClause, FollowingOperatorType.And);


            // Set new grid display
            BeginSetDisplayFilter(newQuery, true);
        }

        #endregion

        #region QuickSearch
        string lastQuickSearch = string.Empty;
        private void NewQuickSearchWanted()
        {
            QuickSearch(txtQuickSearch.Text, lastQuickSearch);
            lastQuickSearch = txtQuickSearch.Text;
        }
        private void QuickSearch(string searchString, string previousText)
        {
            // Get current query
            SimpleSqlSelectStatementInfo newQuery = this.AppEngine.CurrentQuery;

            string fullClauseText = string.Format("{0} like('%{1}%')", Constants.FIELD_NAME_MSG, searchString);
            string previousFullClauseText = string.Format("{0} like('%{1}%')", Constants.FIELD_NAME_MSG, previousText);

            // Find if there's a where clause for message
            WhereClause qsClause = newQuery.WhereClauses.FindSearchClauseByText(previousFullClauseText);

            // Set quick-search clause or remove it?
            if (!string.IsNullOrEmpty(searchString))
            {
                Logging.Writeline("QuickSearch(): For '{0}'", searchString);

                // Build new clause for "message"


                // Either modify the clause or add it
                if (qsClause == null)
                {
                    qsClause = new WhereClause(fullClauseText);
                    newQuery.WhereClauses.Add(qsClause, FollowingOperatorType.And);
                }
                else
                {
                    qsClause.Text = fullClauseText;
                }
            }
            else
            {
                if (qsClause == null)
                {
                    // Good
                }
                else
                {
                    newQuery.WhereClauses.RemoveByText(previousFullClauseText);
                }
            }


            // Set new grid display
            BeginSetDisplayFilter(newQuery, true);
        }

        #endregion

        #region Add/Remove Filters
        void AddLevelExclusionFilter(string value)
        {
            // Build new clause
            string wantedClauseText = string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, value);

            // Try finding the existing clause
            WhereClause tagClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(wantedClauseText);

            // Define all clauses


            if (tagClause == null)
            {
                // Try and find parent nested clause
                WhereClause verboseClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(Constants.FILTER_CLAUSE_VERBOSE);
                WhereClause infoClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(Constants.FILTER_CLAUSE_INF);
                WhereClause mediumClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(Constants.FILTER_CLAUSE_WARNING);
                WhereClause highClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(Constants.FILTER_CLAUSE_HIGH);
                WhereClause criticalClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(Constants.FILTER_CLAUSE_CRITICAL);
                WhereClause exceptionClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(Constants.FILTER_CLAUSE_EXCEPTION);
                WhereClause unexpectedClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(Constants.FILTER_CLAUSE_UNEXPECTED);

                // If there's no level clause added at all, create level clauses container clause
                NestedWhereClause filterParent = null;

                // I'm not proud of this
                // The point is to keep all auto-generated level clauses in the same logical clause group
                bool noParentClausesToAddThisNewClauseInto = (
                        (verboseClause == null || verboseClause.Parent == null) &
                        (infoClause == null || infoClause.Parent == null) &
                        (mediumClause == null || mediumClause.Parent == null) &
                        (highClause == null || highClause.Parent == null) &
                        (criticalClause == null || criticalClause.Parent == null) &
                        (exceptionClause == null || exceptionClause.Parent == null) &
                        (unexpectedClause == null || unexpectedClause.Parent == null)
                    );
                if (noParentClausesToAddThisNewClauseInto)
                {
                    // Create new parent clause
                    filterParent = new NestedWhereClause();
                    this.AppEngine.CurrentQuery.WhereClauses.Add(filterParent, FollowingOperatorType.And);
                }
                else
                {
                    // Find existing parent clause
                    if (!GetClauseParent(verboseClause, out filterParent))
                        if (!GetClauseParent(infoClause, out filterParent))
                            if (!GetClauseParent(mediumClause, out filterParent))
                                if (!GetClauseParent(highClause, out filterParent))
                                    if (!GetClauseParent(criticalClause, out filterParent))
                                        if (!GetClauseParent(exceptionClause, out filterParent))
                                            if (!GetClauseParent(unexpectedClause, out filterParent))
                                                throw new InvalidOperationException("Cannot find parent auto-filter clause to add new clause to");

                }
                tagClause = new WhereClause(wantedClauseText);

                // Remember parent
                tagClause.Parent = filterParent;
                filterParent.Children.Add(tagClause, FollowingOperatorType.And);
            }
            else
            {
                tagClause.Text = wantedClauseText;
            }
        }

        bool GetClauseParent(WhereClause clauseThatMightHaveTheParentWeWant, out NestedWhereClause clauseWithWantedParent)
        {
            if (clauseThatMightHaveTheParentWeWant == null || clauseThatMightHaveTheParentWeWant.Parent == null)
            {
                clauseWithWantedParent = null;
                return false;
            }
            else
            {
                clauseWithWantedParent = clauseThatMightHaveTheParentWeWant.Parent;
                return true;
            }
        }

        void RemoveLevelExclusionFilter(string text)
        {
            string wantedClauseText = string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, text);

            WhereClause tagClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(wantedClauseText);
            if (tagClause == null)
            {
                // Good
            }
            else
            {
                // Remove clause
                this.AppEngine.CurrentQuery.WhereClauses.RemoveByText(wantedClauseText);

                WhereClause levelNestedClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText("()");
                if (levelNestedClause != null && levelNestedClause.Children.Items.Count == 0)
                {
                    this.AppEngine.CurrentQuery.WhereClauses.Items.Remove(levelNestedClause);
                }
            }
        }
        #endregion

        /// <summary>
        /// A level filter button has been pressed
        /// </summary>
        private void UpdateQueryWithLevelAutoFilters()
        {
            // Save any manual changes now
            if (this.QueryIsDirty)
            {
                SimpleSqlSelectStatementInfo userTypedQuery = new SimpleSqlSelectStatementInfo(txtFilter.Text);
                string msg = string.Empty;
                bool userTypedQueryIsValid = this.AppEngine.QueryIsValid(userTypedQuery, out msg);

                if (!userTypedQueryIsValid)
                {
                    MessageBox.Show("Cannot apply auto-filter as the current query needs some attention - specifically:\n\n" + msg, "Auto-filter", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    CheckIfFilterButtonCheckStatesNeedUpdating();
                    return;
                }
                else
                {
                    this.AppEngine.SetNewQuery(userTypedQuery);
                }
            }

            bool
                showVerbose = btnShowVerbose.Checked,
                showInfo = btnShowInfo.Checked,
                showWarning = btnShowWarning.Checked,
                showHigh = btnShowHigh.Checked,
                showCritical = btnShowCritical.Checked,
                showUnexpected = btnShowUnexpected.Checked,
                showExceptions = btnShowException.Checked;

            Logging.Writeline("MainForm.UpdateFilters(): Filtering 'showVerbose={0}, showInfo={1}, showWarning={2}, showHigh={3}, showCritical={4}, showUnexpected={5}, showExceptions={6}'",
                showVerbose,
                showInfo,
                showWarning,
                showHigh,
                showCritical,
                showUnexpected,
                showExceptions);

            // Add/remove clauses
            if (!showVerbose) AddLevelExclusionFilter(Constants.LEVEL_VERBOSE);
            else RemoveLevelExclusionFilter(Constants.LEVEL_VERBOSE);

            if (!showInfo) AddLevelExclusionFilter(Constants.LEVEL_INF);
            else RemoveLevelExclusionFilter(Constants.LEVEL_INF);

            if (!showWarning) AddLevelExclusionFilter(Constants.LEVEL_WANRING);
            else RemoveLevelExclusionFilter(Constants.LEVEL_WANRING);

            if (!showHigh) AddLevelExclusionFilter(Constants.LEVEL_HIGH);
            else RemoveLevelExclusionFilter(Constants.LEVEL_HIGH);

            if (!showCritical) AddLevelExclusionFilter(Constants.LEVEL_CRITICAL);
            else RemoveLevelExclusionFilter(Constants.LEVEL_CRITICAL);

            if (!showUnexpected) AddLevelExclusionFilter(Constants.LEVEL_UNEXPECTED);
            else RemoveLevelExclusionFilter(Constants.LEVEL_UNEXPECTED);

            if (!showExceptions) AddLevelExclusionFilter(Constants.LEVEL_EXCEPTION);
            else RemoveLevelExclusionFilter(Constants.LEVEL_EXCEPTION);

            // Moved to AppEngine_FilterValidated
            // txtFilter.Text = this.ViewQueryInfo.ToString(true);

            // Set new grid display
            BeginSetDisplayFilter(this.AppEngine.CurrentQuery, false);       // Dirty check taken care of above
        }

        /// <summary>
        /// Sets a default view query
        /// </summary>
        private void ResetViewQueryToDefault(bool confirmWithUser)
        {
            if (confirmWithUser && MessageBox.Show("Reset view query to default?", "Confirm", MessageBoxButtons.OKCancel, MessageBoxIcon.Question)
                == System.Windows.Forms.DialogResult.Cancel)
            {
                return;
            }
            SimpleSqlSelectStatementInfo defaultViewQuery = new SimpleSqlSelectStatementInfo(
                        string.Format("Select "
                            + Constants.FIELD_NAME_TIMESTAMP + ", "
                            + Constants.FIELD_NAME_PROCESS + ", "
                            + Constants.FIELD_NAME_TID + ", "
                            + Constants.FIELD_NAME_PRODUCT + ", "
                            + Constants.FIELD_NAME_CATEGORY + ", "
                            + Constants.FIELD_NAME_EVENTID + ", "
                            + Constants.FIELD_NAME_LEVEL + ", "
                            + Constants.FIELD_NAME_MSG + ", "
                            + Constants.FIELD_NAME_CORRELATION + ", "
                            + Constants.FIELD_NAME_SOURCE + ", "
                            + " from "
                            + Constants.TABLE_NAME_LOGDATA)
                    );


            SetDisplayFilter(defaultViewQuery);
            ProcessQueryKeywordHighlighting();
        }

        private void CheckIfFilterButtonCheckStatesNeedUpdating()
        {
            bool haveVerboseFilter = QueryContainFilter(string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, Constants.LEVEL_VERBOSE));
            if (haveVerboseFilter == btnShowVerbose.Checked)
                btnShowVerbose.Checked = !haveVerboseFilter;

            bool haveInfoFilter = QueryContainFilter(string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, Constants.LEVEL_INF));
            if (haveInfoFilter == btnShowInfo.Checked)
                btnShowInfo.Checked = !haveInfoFilter;

            bool haveWarningFilter = QueryContainFilter(string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, Constants.LEVEL_WANRING));
            if (haveWarningFilter == btnShowWarning.Checked)
                btnShowWarning.Checked = !haveWarningFilter;

            bool haveHighFilter = QueryContainFilter(string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, Constants.LEVEL_HIGH));
            if (haveHighFilter == btnShowHigh.Checked)
                btnShowHigh.Checked = !haveHighFilter;

            bool haveCriticalFilter = QueryContainFilter(string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, Constants.LEVEL_CRITICAL));
            if (haveCriticalFilter == btnShowCritical.Checked)
                btnShowCritical.Checked = !haveCriticalFilter;

            bool haveUnexpectedLevelFilter = QueryContainFilter(string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, Constants.LEVEL_UNEXPECTED));
            if (haveUnexpectedLevelFilter == btnShowUnexpected.Checked)
                btnShowUnexpected.Checked = !haveUnexpectedLevelFilter;

            bool haveExceptionFilter = QueryContainFilter(string.Format("{0} != '{1}'", Constants.FIELD_NAME_LEVEL, Constants.LEVEL_EXCEPTION));
            if (haveExceptionFilter == btnShowException.Checked)
                btnShowException.Checked = !haveExceptionFilter;

        }

        private bool QueryContainFilter(string clauseText)
        {
            WhereClause tagClause = this.AppEngine.CurrentQuery.WhereClauses.FindSearchClauseByText(clauseText);
            if (tagClause == null)
                return false;
            else
                return true;
        }

        #endregion

    }
}
